package cn.jofei.netty.hadler;

import cn.jofei.common.config.AppProperties;
import cn.jofei.common.monitor.msg.DingMsg;
import cn.jofei.common.netty.message.Message;
import cn.jofei.common.utils.Utils;
import cn.jofei.netty.server.SocketTCPServer;
import cn.jofei.server.interf.ServerQueue;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Promise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Set;

import static cn.jofei.common.netty.message.MsgType.*;
import static cn.jofei.server.interf.ServerQueue.*;

/**
 * 客户端信息处理
 *
 * @author Jofei Bao
 * @version ngbao v2.0
 * @date 2022年10月17日 21:01
 */
@ChannelHandler.Sharable
public class ClientMessageHandler extends SimpleChannelInboundHandler<Message> {

    private final Logger logger = LoggerFactory.getLogger(ClientMessageHandler.class);

    private static final String DOMAIN = AppProperties.get("server.domain");

    private static final String NOT_MACH_INFO = "版本已过期，请下载最新版：【http://cloud." + AppProperties.get("server.domain") + "/#/share?uid=" + AppProperties.get("share.uid.ngbao") + "】或带环境版本：【http://cloud." + AppProperties.get("server.domain") + "/#/share?uid=" + AppProperties.get("share.uid.ngbao-with-jre") + "】";

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
        logger.info("客户端创建连接，IP：{}，端口：{}", address.getHostName(), address.getPort());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
        logger.info("与客户端断开连接: IP：{}，端口：{}", address.getHostName(), address.getPort());
        Set<String> keys = ServerQueue.CLIENT_REG_INFO_MAPPING.remove(String.format("%s:%s", address.getHostName(), address.getPort()));
        if (Utils.nonNull(keys)) {
            for (String key : keys) {
                CLIENT_REG_INFO.remove(key);
                logger.info("移除客户端：{}", key);
                if (Utils.isNum(key)) {
                    Integer port = Integer.valueOf(key);
                    Channel channel = REG_TCP_CHANNELS.remove(port);
                    if (channel != null) {
                        logger.info("停止监听TCP端口：{}", port);
                        channel.close();
                    }
                }
            }
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Message message) throws Exception {
        InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
        try {
            if (Utils.match(message)) {
                int msgType = message.getMsgType();
                String key;
                switch (msgType) {
                    case TCP_REG:
                        key = message.getPort() + "";
                        if (CLIENT_REG_INFO.putIfAbsent(key, ctx) != null) {
                            ctx.writeAndFlush(message
                                    .setSuccess(false)
                                    .setMsg("远程 TCP 端口 [" + message.getPort() + "] 已经被注册！请更换其他端口。")
                                    .setMsgType(REG_RESPONSE)
                                    .reCode()
                            );
                        } else {
                            //添加映射
                            putMapping(address, key);
                            //创建TCP端口监听
                            new SocketTCPServer(message.getPort()).start();

                            ctx.writeAndFlush(message
                                    .setSuccess(true)
                                    .setMsgType(REG_RESPONSE)
                                    .reCode()
                            );

                            DingMsg.markdown("Ngbao监控").text(String.format("#### Ngbao监控\n#### 客户端注册TCP连接\n##### 外网端口：%s\n##### 客户端IP：%s\n##### 客户端端口：%s", message.getPort(), address.getHostName(), address.getPort())).build().send();

                            logger.info("注册TCP连接成功，外网端口：{}，客户端IP：{}，端口：{}",
                                    message.getPort(),
                                    address.getHostName(),
                                    address.getPort());
                        }
                        break;
                    // 注册
                    case HTTP_REG:
                        key = message.getSubdomain();

                        if (CLIENT_REG_INFO.putIfAbsent(message.getSubdomain(), ctx) != null) {
                            ctx.writeAndFlush(message
                                    .setSuccess(false)
                                    .setMsg("子域名 [" + message.getSubdomain() + "] 已经被注册！ 请更换其他子域名。")
                                    .setMsgType(REG_RESPONSE)
                                    .reCode()
                            );
                        } else {
                            ctx.writeAndFlush(message
                                    .setSuccess(true)
                                    .setMsgType(REG_RESPONSE)
                                    .reCode()
                            );

                            //添加映射
                            putMapping(address, key);

                            String subdomain = message.getSubdomain() + "." + DOMAIN;

                            DingMsg.markdown("Ngbao监控").text(String.format("#### Ngbao监控\n#### 客户端注册HTTP转发\n##### 地址：http://%s\n##### 客户端IP：%s\n##### 客户端端口：%s", subdomain, address.getHostName(), address.getPort())).build().send();

                            logger.info("注册HTTP转发成功，域名：{}，客户端IP：{}，端口：{}",
                                    subdomain,
                                    address.getHostName(),
                                    address.getPort());
                        }
                        break;
                    case TCP_RESPOND:
                        // 信息响应
                    case HTTP_RESPOND:
                        Promise<Channel> promise = REQUEST_QUEUE_NETTY.remove(message.getSequenceId());
                        if (promise != null) {
                            //添加通道信息
                            promise.setSuccess(ctx.channel());
                        } else {
                            logger.error("未找到外网通道！SequenceId：{}", message.getSequenceId());
                        }
                        break;
                }
            } else {
                ctx.writeAndFlush(message
                        .setSuccess(false)
                        .setMsg(NOT_MACH_INFO)
                        .setMsgType(REG_RESPONSE)
                        .reCode()
                );
                logger.warn("旧版客户端注册，客户端IP：{}，端口：{}",
                        address.getHostName(),
                        address.getPort());
            }
        } finally {
            ReferenceCountUtil.safeRelease(message);
        }
    }

    /**
     * 客户端映射关系
     *
     * @param address 客户端的通信 IP : 端口
     * @param value   客户端的 注册 值
     */
    private void putMapping(InetSocketAddress address, String value) {
        String key = String.format("%s:%s", address.getHostName(), address.getPort());
        if (ServerQueue.CLIENT_REG_INFO_MAPPING.containsKey(key)) {
            ServerQueue.CLIENT_REG_INFO_MAPPING.get(key).add(value);
        } else {
            Set<String> set = new HashSet<>();
            set.add(value);
            ServerQueue.CLIENT_REG_INFO_MAPPING.putIfAbsent(key, set);
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            switch (event.state()) {
                case ALL_IDLE:
                    logger.debug("读写都超时");
                    break;
                case READER_IDLE:
                    logger.warn("未收到客户端心跳，断开现有连接");
                    ctx.close();
                    break;
                case WRITER_IDLE:
                    logger.debug("写超时");
                    break;
            }
        }
    }
}
